using HamsterDocs.Domains.Docs;
using HamsterDocs.Domains.Storage;
using HamsterDocs.Domains.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace HamsterDocs.EntityFrameworkCore;

public class MasterDbContext<TDbContext>(DbContextOptions<TDbContext> options)
    : DbContext(options), IContext where TDbContext : DbContext
{
    // DbSets
    public DbSet<User> Users => Set<User>();
    public DbSet<ExternalLogin> ExternalLogins => Set<ExternalLogin>();

    public DbSet<DocsProject> DocsProjects => Set<DocsProject>();
    public DbSet<DocsTree> DocsTrees => Set<DocsTree>();
    public DbSet<DocsItem> DocsItems => Set<DocsItem>();

    public DbSet<LocalStorage> LocalStorages => Set<LocalStorage>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Users
        modelBuilder.Entity<User>(ConfigureUser);

        // ExternalLogin
        modelBuilder.Entity<ExternalLogin>(b =>
        {
            b.HasIndex(x => new { x.Provider, x.ProviderKey }).IsUnique();
            b.HasOne<User>()
             .WithMany()
             .HasForeignKey(x => x.UserId)
             .OnDelete(DeleteBehavior.Cascade);
        });

        // DocsProject
        modelBuilder.Entity<DocsProject>(b =>
        {
            b.Property(x => x.Name).IsRequired().HasMaxLength(128);
            b.HasIndex(x => new { x.UserId, x.Name }).IsUnique();
        });

        // DocsTree
        modelBuilder.Entity<DocsTree>(b =>
        {
            b.Property(x => x.Name).HasMaxLength(128);
            b.Property(x => x.Description).HasMaxLength(512);
            b.HasIndex(x => new { x.UserId, x.DocsProjectId });
        });

        // DocsItem with one-to-one to DocsTree
        modelBuilder.Entity<DocsItem>(b =>
        {
            b.HasIndex(x => x.DocsTreeId).IsUnique();
            b.Property(x => x.Content).HasColumnType("TEXT");
            b.HasOne<DocsTree>()
             .WithOne()
             .HasForeignKey<DocsItem>(x => x.DocsTreeId)
             .OnDelete(DeleteBehavior.Cascade);
        });

        // LocalStorage
        modelBuilder.Entity<LocalStorage>(b =>
        {
            b.Property(x => x.Path).HasMaxLength(256);
            b.Property(x => x.FileHash).HasMaxLength(64);
            b.Property(x => x.ThumbPath).HasMaxLength(256);
            b.HasIndex(x => x.UserId);
        });
    }

    private static void ConfigureUser(EntityTypeBuilder<User> b)
    {
        b.Property(x => x.UserName).HasMaxLength(64);
        b.Property(x => x.Email).HasMaxLength(128);
        b.Property(x => x.Role).HasMaxLength(16);

        b.HasIndex(x => x.Email).IsUnique();
        b.HasIndex(x => x.UserName).IsUnique();
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        BeforeSaveChanges();
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        BeforeSaveChanges();
        return base.SaveChangesAsync(cancellationToken);
    }

    private void BeforeSaveChanges()
    {
        var now = DateTime.UtcNow;
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Added)
            {
                TrySetProperty(entry, "CreatedAt", now);
                TrySetProperty(entry, "UpdatedAt", now);
            }
            else if (entry.State == EntityState.Modified)
            {
                TrySetProperty(entry, "UpdatedAt", now);
                // For docs item last modified
                TrySetProperty(entry, "LastModified", now);
            }
        }
    }

    private static void TrySetProperty(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entry, string propertyName, object value)
    {
        var prop = entry.Metadata.FindProperty(propertyName);
        if (prop != null)
        {
            entry.Property(propertyName).CurrentValue = value;
        }
    }
}